﻿using Devil;
using GameToolkit.BehaviourTree;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using System.Text.RegularExpressions;
using UnityEditor;
using UnityEditor.Experimental.GraphView;
using UnityEngine;

namespace GameToolkit.Editor
{
    public class BehaviourTreeLib
    {

        internal enum EBTNodeType
        {
            Invalid,
            Controller,
            Behaviour,
            Decorator,
            Condition,
            ComplexLogic,
            Register, 
            RegisterRef,
        }

        static readonly string[] NodeTypeNames = {
        "Invalid",
        "控制节点 (Controller)",
        "行为节点 (Action)",
        "装饰节点 (Decoration)",
        "条件节点 (Logic)",
        "逻辑节点 (Logic)",
        "寄存器 (Register)",
        "寄存器 (Register)"
        };

        static readonly string BEHAVIUOR_TREE_ICON = "2b6bdb18a30c4e94c9aa071f06ea25c9";
        static readonly string CONTROLLER_ICON = "2b6bdb18a30c4e94c9aa071f06ea25c9";
        static readonly string BEHAVIOUR_ICON = "5be919ef08cb9734aa45334691487554";
        static readonly string DECORATOR_ICON = "89766e0c245f0a34fbe4e996e6a2b121";
        static readonly string CONDITION_ICON = "c427a93c8ea37414eab7ebc7bff4e73b";

        internal string GetCategory(EBTNodeType type)
        {
            return NodeTypeNames[(int)type];
        }

        internal enum EPortType
        {
            None,
            Single,
            Multi,
        }

        internal class CompositeInfo
        {

            JsonSerializedObject.TypeWrapper mWrapper;
            string mName;
            string mDesc;
            string mCategory;
            bool mIsSubTree;
            EBTNodeType mNodeType;
            EPortType mInputPort;
            int mDepth;
            public bool IsSubTreeRoot => mIsSubTree;
            public JsonSerializedObject.TypeWrapper WrapType => mWrapper;
            public EBTNodeType NodeType => mNodeType;
            public string Title => mName;
            public string Descript => mDesc;
            public string Category => mCategory;
            public string Namespace => mWrapper.WrapType.Namespace;
            public int Depth => mDepth;
            public bool IsExecutableNode => mNodeType == EBTNodeType.Behaviour || mNodeType == EBTNodeType.Controller;
            public EPortType HasInputPort => mInputPort;

            string mIconPath;
            Texture2D mIcon;
            public Texture2D Icon
            {
                get
                {
                    if (mIcon == null && !string.IsNullOrEmpty(mIconPath))
                    {
                        if (Regex.IsMatch(mIconPath, "^[0123456789abcdef]{32}$", RegexOptions.IgnoreCase))
                            mIconPath = AssetDatabase.GUIDToAssetPath(mIconPath);
                        mIcon = AssetDatabase.LoadAssetAtPath<Texture2D>(mIconPath);
                        if (mIcon == null)
                            mIconPath = null;
                    }
                    return mIcon;
                }
            }

            public CompositeInfo(CompositeInfo wrapType)
            {
                mNodeType = wrapType.mNodeType;
                mWrapper = wrapType.mWrapper;
                mIsSubTree = wrapType.mIsSubTree;
                mInputPort = wrapType.mInputPort;
                mName = wrapType.mName;
                mDesc = wrapType.mDesc;
                mCategory = $"{NodeTypeNames[(int)mNodeType]}/{wrapType.mCategory}";
                mIcon = wrapType.mIcon;
                mIconPath = wrapType.mIconPath;
                mDepth = wrapType.Depth;
            }

            public CompositeInfo(JsonSerializedObject.TypeWrapper wrapper)
            {
                mWrapper = wrapper;
                var tp = wrapper.WrapType;
                mNodeType = EBTNodeType.Invalid;
                bool asParent = false;
                if (GlobalUtil.IsTypeInheritedFrom(tp, typeof(Composite), false))
                {
                    try
                    {
                        var inst = wrapper.Create() as Composite;
                        if (inst is LogicWithInputComposite logic)
                        {
                            mInputPort = logic.IsMultiInput ? EPortType.Multi : EPortType.Single;
                            mNodeType = mInputPort == EPortType.Single ? EBTNodeType.Condition : EBTNodeType.ComplexLogic;
                        }
                        else if(inst is RegisterComposite)
                        {
                            mNodeType = EBTNodeType.Register;
                            mInputPort = EPortType.Single;
                        }
                        else if(inst is RegisterReference)
                        {
                            mNodeType = EBTNodeType.RegisterRef;
                            mInputPort = EPortType.None;
                        }
                        else
                        {
                            var obj = inst?.Create();
                            asParent = obj is IExecutableParent;
                            if (obj is ControllerNode)
                                mNodeType = EBTNodeType.Controller;
                            else if (obj is ActionNode)
                                mNodeType = EBTNodeType.Behaviour;
                            else if (obj is Decoration)
                                mNodeType = obj is IInterruptionSource ? EBTNodeType.Condition : EBTNodeType.Decorator;
                            mInputPort = mNodeType == EBTNodeType.Behaviour || mNodeType == EBTNodeType.Controller ? EPortType.Single : EPortType.None;
                        }
                    }
                    catch
                    {
                        mNodeType = EBTNodeType.Invalid;
                    }
                }
                if (mNodeType != EBTNodeType.Invalid)
                {
                    var ntype = wrapper.WrapType;
                    mIsSubTree = mNodeType == EBTNodeType.Controller || asParent;
                    var cate = ntype.GetCustomAttribute<CompositeCategoryAttribute>();
                    var title = ntype.GetCustomAttribute<CompositeTitleAttribute>();
                    var icon = ntype.GetCustomAttribute<CompositeIconAttribute>();
                    if (title == null)
                    {
                        mName = ParallelUtils.ToCamelName(ntype.Name, true);
                        mDesc = ntype.FullName;
                    }
                    else
                    {
                        mName = title.Title;
                        mDesc = title.Description;
                        if (string.IsNullOrEmpty(mName))
                            mName = ParallelUtils.ToCamelName(ntype.Name, true);
                        if (string.IsNullOrEmpty(mDesc))
                            mDesc = ntype.FullName;
                    }
                    //var nodename = NodeTypeNames[(int)mNodeType];
                    if (cate == null)
                    {
                        var nspace = ntype.Namespace;
                        if (string.IsNullOrEmpty(nspace))
                            nspace = "Default";
                        else
                            nspace = nspace.Replace('.', '/');
                        mCategory = $"{nspace}/{mName} ({ntype.Name})";
                    }
                    else if (string.IsNullOrEmpty(cate.Category))
                    {
                        mCategory = $"{mName} ({ntype.Name})";
                    }
                    else
                    {
                        mCategory = $"{cate.Category}/{mName} ({ntype.Name})";
                    }
                    mDepth = 1;
                    for (int i = mCategory.Length - 2; i > 0; i--)
                    {
                        if (mCategory[i] == '/')
                            mDepth++;
                    }
                    if (icon == null || string.IsNullOrEmpty(icon.Icon))
                    {
                        if (mNodeType == EBTNodeType.Behaviour)
                            mIconPath = BEHAVIOUR_ICON;
                        else if (mNodeType == EBTNodeType.Controller)
                            mIconPath = CONTROLLER_ICON;
                        else if (mNodeType == EBTNodeType.Condition)
                            mIconPath = CONDITION_ICON;
                        else
                            mIconPath = DECORATOR_ICON;
                    }
                    else
                    {
                        mIconPath = icon.Icon;
                    }
                }
            }

            internal void AppendToSearchTreeList(IList<SearchTreeEntry> searchLst)
            {
                SearchTreeProviderUtils.AppendToSearchTreeList(this, mCategory, searchLst);
            }

            internal Node CreateGraphNode(Composite context, Vector2 initPos)
            {
                if (context == null)
                {
                    context = mWrapper.Create() as Composite;
                    if (context != null)
                    {
                        context._guid = GUID.Generate().ToString();
                        context.m_Position = initPos;
                    }
                }
                if (context == null)
                    return null;

                if (NodeType == EBTNodeType.Behaviour || NodeType == EBTNodeType.Controller)
                    return new ExecutableNodeVisual(this, context);
                else if (NodeType == EBTNodeType.Register || NodeType == EBTNodeType.RegisterRef)
                    return new RegisterNodeVisual(this, context);
                else
                    return new LogicNodeVisual(this, context);
            }
        }

        List<SearchTreeEntry> mLoadingSearchTree = new List<SearchTreeEntry>() { new SearchTreeGroupEntry(new GUIContent("Is Loading...")) };

        // controller & behaviour
        List<SearchTreeEntry> mSearchTreeLst;
        // decorators
        List<SearchTreeEntry> mDecoratorSearchTreeLst;
        Texture2D mBehaviourTreeIcon;

        Dictionary<Type, CompositeInfo> mNodesLib = new Dictionary<Type, CompositeInfo>();
        public List<SearchTreeEntry> ControllerAndBehaviuorSearchTree => mSearchTreeLst == null ? mLoadingSearchTree : mSearchTreeLst;
        public List<SearchTreeEntry> DecoratorSearchTree => mDecoratorSearchTreeLst == null ? mLoadingSearchTree : mDecoratorSearchTreeLst;
        public Texture2D TreeIcon
        {
            get
            {
                if (mBehaviourTreeIcon == null)
                {
                    var path = AssetDatabase.GUIDToAssetPath(BEHAVIUOR_TREE_ICON);
                    if (!string.IsNullOrEmpty(path))
                        mBehaviourTreeIcon = AssetDatabase.LoadAssetAtPath<Texture2D>(path);
                }
                return mBehaviourTreeIcon;
            }
        }

        private BehaviourTreeLib()
        {
            LoadLibs();
        }

        void LoadLibs()
        {
            JsonSerializedObject.InitWrappers();
            var lib = new Dictionary<Type, CompositeInfo>();
            var lst = new List<JsonSerializedObject.TypeWrapper>();
            Action<JsonSerializedObject.TypeWrapper> wrapAction = (wrap) =>
            {
                if (GlobalUtil.IsTypeInheritedFrom(wrap.WrapType, typeof(Composite)))
                    lst.Add(wrap);
            };
            JsonSerializedObject.VisitAllWrappers(wrapAction);
            var nodeLib = new List<SearchTreeEntry>();
            nodeLib.Add(new SearchTreeGroupEntry(new GUIContent("Behaviour Tree Nodes")));
            var decLib = new List<SearchTreeEntry>();
            decLib.Add(new SearchTreeGroupEntry(new GUIContent("Behaviour Tree Decorators")));
            for (int i = 0; i < lst.Count; i++)
            {
                var info = new CompositeInfo(lst[i]);
                if (info.NodeType != EBTNodeType.Invalid)
                {
                    lib[info.WrapType.WrapType] = info;
                    if (info.NodeType == EBTNodeType.Register)
                    {
                        info.AppendToSearchTreeList(nodeLib);
                    }
                }
            }
            var coms = new MinHeap<CompositeInfo>(lib.Count, (a, b) => a.NodeType == b.NodeType ? (a.Depth - b.Depth) : (b.NodeType - a.NodeType));
            foreach(var info in lib.Values)
            {
                coms.Add(info);
            }
            //foreach(var info in lib.Values)
            while(coms.Count > 0)
            {
                var info = coms.RemoveTop();
                if (info.NodeType == EBTNodeType.Behaviour || info.NodeType == EBTNodeType.Controller || info.NodeType == EBTNodeType.ComplexLogic)
                {
                    new CompositeInfo(info).AppendToSearchTreeList(nodeLib);
                }
                else if (info.NodeType == EBTNodeType.Decorator)
                {
                    info.AppendToSearchTreeList(decLib);
                }
                else if (info.NodeType == EBTNodeType.Condition)
                {
                    info.AppendToSearchTreeList(decLib);
                    if (info.WrapType.WrapType.GetCustomAttribute<DecorationCompositeOnlyAttribute>() == null)
                        new CompositeInfo(info).AppendToSearchTreeList(nodeLib);
                }
            }
            if (nodeLib.Count == 1)
            {
                nodeLib[0].content.text = "No Avaliable Nodes...";
            }
            mNodesLib = lib;
            mSearchTreeLst = nodeLib;
            mDecoratorSearchTreeLst = decLib;
            Debug.Log($"BehaviourTreeLib was loaded.");
        }

        internal CompositeInfo GetCompositeInfo(Composite composite)
        {
            if (composite == null)
                return null;
            CompositeInfo info;
            if (mNodesLib.TryGetValue(composite.GetType(), out info))
                return info;
            return null;
        }

        internal CompositeInfo GetCompositeInfo(Type type)
        {
            CompositeInfo info;
            if (mNodesLib.TryGetValue(type, out info))
                return info;
            return null;
        }

        internal string GetTitle(Composite btasset, bool includeTypeName)
        {
            if (btasset == null)
                return "<nothing>"; ;
            var info = GetCompositeInfo(btasset.GetType());
            if (info == null)
                return "<unknown>";
            if (includeTypeName)
                return $"{info.Title} ({info.WrapType.WrapType.Name})";
            else
                return info.Title;
        }

        internal string GetDescription(Composite btasset, bool getRuntimeInfo = true, bool includeTypeName = false)
        {
            if (btasset == null)
                return "<nothing>";
            if (getRuntimeInfo)
            {
                var desc = btasset.GetDescription();
                if (!string.IsNullOrEmpty(desc))
                {
                    if (includeTypeName)
                        return $"{desc} ({btasset.GetType().Name})";
                    else
                        return desc;
                }
            }
            var info = GetCompositeInfo(btasset.GetType());
            if (info == null)
                return "<unknown>";
            if (includeTypeName)
                return $"{info.Descript} ({info.WrapType.WrapType.Name})";
            else
                return info.Descript;
        }

        static BehaviourTreeLib sInstance;
        public static BehaviourTreeLib Instance
        {
            get
            {
                if (sInstance == null)
                {
                    sInstance = new BehaviourTreeLib();
                }
                return sInstance;
            }
        }


        [InitializeOnLoadMethodParallel]
        static IEnumerator Initialize()
        {
            BehaviourTreeLib inst;
            if (sInstance == null)
            {
                sInstance = new BehaviourTreeLib();
            }
            else
            {
                sInstance.LoadLibs();
            }
            inst = sInstance;
            yield return ParallelUtils.Schedule(() =>
            {
                foreach (var entry in inst.mSearchTreeLst)
                {
                    if (entry.userData is CompositeInfo cinfo)
                        entry.content.image = cinfo.Icon;
                }
                foreach (var entry in inst.mDecoratorSearchTreeLst)
                {
                    if (entry.userData is CompositeInfo cinfo)
                        entry.content.image = cinfo.Icon;
                }
            }, true);
        }
    }
}
